package beanstao.util.store.dal;

import java.sql.Date;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;

import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;

import beanstao.util.Dates;
import beanstao.util.id.DId;
import beanstao.util.id.Id;
import beanstao.util.json.JsonLib;
import beanstao.util.reflect.Mirror;
import beanstao.util.store.dal.interfaces.IDomainXmlSelect;
import beanstao.util.store.dal.interfaces.IDomainXmlUpdate;
import beanstao.util.store.database.Fields;
import beanstao.util.store.database.Where;
import beanstao.util.store.domainobject.DomainList;
import beanstao.util.store.domainobject.XmlDomain;
import beanstao.util.store.domainobject.fields.PublicField;
import beanstao.util.xml.XDocument;


/**
 * 这是一个针对DomainObject的Dal的“XML格式”的抽象类(简单格式，非树状)，所有的持久化方法均已经基本实现。<BR>
 * 
 * @author beanstao@google.com 
 * @param <T>
 */
public abstract class XmlDal<T extends XmlDomain> extends Dal<T> implements IDomainXmlUpdate<XmlDomain>, IDomainXmlSelect<XmlDomain>
{
	/**
	 * 当前Domain所保存的XML文件
	 */
	protected XDocument _OwnerDocument;
	/**
	 * 当前Domain所针对的Element根节点
	 */
	protected Element _CoreElement;
	/**
	 * 当前Domain所针对的Element的字段
	 */
	protected Fields _Fields = null;

	public XmlDal(final XDocument document, final Fields fields)
	{
		if ((null == document) || (null == fields))
			this.logError("实体的DAL初始化参数不全。");// , (new
		// ParameterIsNullOrEmptyException()));

		this._OwnerDocument = document;
		this._Fields = fields;
		this.setCurrElement();
	}

	/**
	 * 设置当前DAL所面向的根节点
	 */
	protected void setCurrElement()
	{
		this._CoreElement = this._OwnerDocument.single(this._Fields.getName());
	}

	@Override
	public boolean create(final T domain)
	{
		try
		{
			Element ele = domain.toXmlElement();
			XDocument.setAttribute(ele, PublicField.CREATED_TIME, Dates.nowToGeneralLongString());
			XDocument.setAttribute(ele, PublicField.IS_DELETED, String.valueOf(0));
			this._CoreElement.add(ele);
			this._OwnerDocument.save();
			return true;
		}
		catch (Exception e)
		{
			return false;
		}
	}

	@Override
	public int create(final DomainList<T> domainList)
	{
		for (T t : domainList)
			this.create(t);
		return domainList.size();
	}

	@Override
	public boolean delete(final T domain)
	{
		try
		{
			String localname = this._Fields.getName();
			String id = domain.getId().toString();
			String xpath = String.format("//%s[@id='%s']", localname, id);
			Id modifior = domain.getModifior();
			Element element = (Element) this._CoreElement.selectSingleNode(xpath);
			XDocument.setAttribute(element, PublicField.IS_DELETED, String.valueOf(1));
			XDocument.setAttribute(element, PublicField.MODIFIOR, modifior.toString());
			XDocument.setAttribute(element, PublicField.MODIFED_TIME, Dates.nowToGeneralLongString());

			this.deleteChildElement(modifior, element);

			this._OwnerDocument.save();
		}
		catch (Exception e)
		{
			this.logWarn("ID为 " + domain.getId() + " 的实体设置被删除失败。", e);
			return false;
		}

		return true;
	}

	@Override
	public int delete(final DomainList<T> domainList)
	{
		for (T t : domainList)
			this.delete(t);
		return domainList.size();
	}

	@Override
	public int delete(final Id modifiorId, final Id dId)
	{
		try
		{
			String xpath = String.format("//%s[@id='%s']", this._Fields.getName(), dId.toString());
			Element element = (Element) this._CoreElement.selectSingleNode(xpath);
			XDocument.setAttribute(element, PublicField.IS_DELETED, String.valueOf(1));
			XDocument.setAttribute(element, PublicField.MODIFIOR, modifiorId.toString());
			XDocument.setAttribute(element, PublicField.MODIFED_TIME, Dates.nowToGeneralLongString());
			this.deleteChildElement(modifiorId, element);
			this._OwnerDocument.save();
		}
		catch (Exception e)
		{
			this.logWarn("ID为 " + dId.toString() + " 的实体设置被删除失败。", e);
			return 0;
		}
		return 1;
	}

	public int delete(final Id modifiorId, final List<Id> dId)
	{
		try
		{
			for (Id id : dId)
				this.delete(modifiorId, id);
		}
		catch (Exception e)
		{
			this.logWarn("ID为 " + dId.toString() + " 的实体设置被删除失败。", e);
			return 0;
		}
		return 1;
	}

	@Override
	@Deprecated
	public int delete(final Id modifiorId, final Where where)
	{
		return 0;
	}

	@Override
	public int delete(final Id modifiorId, final String key, final Object value)
	{
		try
		{
			String xpath = String.format("//%s[@%s='%s']", this._Fields.getName(), key, value);
			Element element = (Element) this._CoreElement.selectSingleNode(xpath);
			XDocument.setAttribute(element, PublicField.IS_DELETED, String.valueOf(1));
			XDocument.setAttribute(element, PublicField.MODIFIOR, modifiorId.toString());
			XDocument.setAttribute(element, PublicField.MODIFED_TIME, Dates.nowToGeneralLongString());

			this.deleteChildElement(modifiorId, element);

			this._OwnerDocument.save();
		}
		catch (Exception e)
		{
			this.logWarn(String.format("具有键为%s，值为%s的实体设置被删除失败。", key, value), e);
			return 0;
		}
		return 1;
	}

	@Override
	public int deleteAll(final Id modifiorId)
	{
		for (Element element : this._CoreElement.elements())
		{
			XDocument.setAttribute(element, PublicField.IS_DELETED, String.valueOf(1));
			XDocument.setAttribute(element, PublicField.MODIFIOR, modifiorId.toString());
			XDocument.setAttribute(element, PublicField.MODIFED_TIME, Dates.nowToGeneralLongString());

			this.deleteChildElement(modifiorId, element);
		}
		this._OwnerDocument.save();
		return this._CoreElement.elements().size();
	}

	@Override
	public boolean remove(final T domain)
	{
		try
		{
			String xpath = String.format("//%s[@id='%s']", this._Fields.getName(), domain.getId());
			if (this._CoreElement.selectSingleNode(xpath).getParent().remove(this._CoreElement.selectSingleNode(xpath)))
			{
				this._OwnerDocument.save();
				return true;
			}
			this._CoreElement.remove(this._CoreElement.selectSingleNode(xpath));
			this._OwnerDocument.save();
			return true;
		}
		catch (Exception e)
		{
			return false;
		}

	}

	@Override
	public int remove(final DomainList<T> domainList)
	{
		for (T t : domainList)
			this.delete(t);
		return domainList.size();
	}

	@Override
	public boolean remove(final Id modifiorId, final Id dId)
	{
		try
		{
			String xpath = String.format("//%s[@id='%s']", this._Fields.getName(), dId.toString());
			Element element = (Element) this._CoreElement.selectSingleNode(xpath);
			this._CoreElement.remove(element);
			this._OwnerDocument.save();
			this.logInfo(String.format("用户%s彻底删除了%s的所有数据。数据的id为：%s", modifiorId, this._CoreElement.getName(), dId.toString()));
		}
		catch (Exception e)
		{
			this.logWarn("ID为 " + dId.toString() + " 的实体设置被删除失败。", e);
			return false;
		}
		return true;
	}

	@Override
	@Deprecated
	public boolean remove(final Id modifiorId, final Where where)
	{
		return false;
	}

	@Override
	public boolean remove(final Id modifiorId, final String key, final Object value)
	{
		int i = 0;
		try
		{
			ArrayList<String> ids = new ArrayList<String>();
			String xpath = String.format("//%s[@%s='%s']", this._Fields.getName(), key, value);
			List<?> elements = this._CoreElement.selectNodes(xpath);
			i = elements.size();
			for (Object object : elements)
			{
				this._CoreElement.remove((Element) object);
				ids.add(((Element) object).attributeValue("id"));
			}
			this._OwnerDocument.save();
			i = 1;
			this.logInfo(String.format("用户%s彻底删除了%s的所有数据。数据的id的集合为：%s", modifiorId, this._CoreElement.getName(), JsonLib.fromObject(ids)));
		}
		catch (Exception e)
		{
			this.logWarn(String.format("具有键为%s，值为%s的实体设置被彻底移除时失败。", key, value), e);
			return 0 == i;
		}
		return 1 == i;
	}

	@Override
	public boolean removeAll(final Id modifiorId)
	{
		try
		{
			ArrayList<String> ids = new ArrayList<String>();
			while (this._CoreElement.elements().size() > 0)
			{
				Element element = this._CoreElement.elements().get(0);
				ids.add(element.attributeValue("id"));
				this._CoreElement.remove(element);
			}
			this._OwnerDocument.save();
			this.logInfo(String.format("用户%s彻底删除了%s的所有数据。数据的id的集合为：%s", modifiorId, this._CoreElement.getName(), JsonLib.fromObject(ids)));
			return true;
		}
		catch (Exception e)
		{
			return false;
		}
	}

	@Override
	public int count(final String xpath)
	{
		List<? extends Node> elements = this._CoreElement.selectNodes(xpath);
		return elements.size();
	}

	@Override
	public boolean exists(final Id id)
	{
		Node node = this._CoreElement.selectSingleNode(String.format(id.toXPath(), this._Fields.getName()));
		return null != node;
	}

	@Override
	public boolean exists(final XmlDomain domain)
	{
		Node node = this._CoreElement.selectSingleNode(String.format(domain.getId().toXPath(), this._Fields.getName()));
		return null == node;
	}

	@Override
	public XmlDomain select(final Id id)
	{
		Element element = (Element) this._CoreElement.selectSingleNode(String.format("//%s[@id='%s']", this._Fields.getName(), id));
		T t = this.newBlankDomain();
		HashMap<String, String> map = XDocument.toMapByAttributes(element);
		t.putAll(map);
		return t;
	}

	@Override
	public DomainList<XmlDomain> select(final String[] fields, final String xpath)
	{
		DomainList<XmlDomain> ts = new DomainList<XmlDomain>();
		List<? extends Node> elements = this._CoreElement.selectNodes(xpath);
		for (Node node : elements)
		{
			if (!(node instanceof Element))
				continue;
			HashMap<String, String> map = XDocument.toMapByAttributes(node);
			T t = this.newBlankDomain();
			t.putAll(map);
			ts.add(t);
		}
		return ts;
	}

	@Override
	public DomainList<XmlDomain> selectAttributeOfAll(final String attrName)
	{
		DomainList<XmlDomain> ts = new DomainList<XmlDomain>();
		String xpath = String.format("//%s/@%s", this._Fields.getName(), attrName);
		List<? extends Node> elements = this._CoreElement.selectNodes(xpath);
		for (Node node : elements)
		{
			HashMap<String, String> map = new HashMap<String, String>();
			map.put(node.getName(), node.getStringValue());
			T t = this.newBlankDomain();
			t.putAll(map);
			ts.add(t);
		}
		return ts;
	}

	@Override
	public DomainList<XmlDomain> select(final String[] fields, final String key, final Object value)
	{
		String xpath = String.format("//%s[@%s='%s']", this._Fields.getName(), key, value);
		DomainList<XmlDomain> ts = new DomainList<XmlDomain>();
		List<? extends Node> elements = this._CoreElement.selectNodes(xpath);
		for (Node node : elements)
		{
			if (!(node instanceof Element))
				continue;
			HashMap<String, String> map = XDocument.toMapByAttributes(node);
			T t = this.newBlankDomain();
			t.putAll(map);
			ts.add(t);
		}
		return ts;
	}

	/**
	 * 基于XML的Select All方法,与Database的实现有一些区别。将返回当前Domain的所有后代。
	 * 
	 * @see beanstao.util.store.dal.interfaces.IDomainXmlSelect#selectAll()
	 */
	@Override
	public DomainList<XmlDomain> selectAll()
	{
		DomainList<XmlDomain> ts = new DomainList<XmlDomain>();
		String xpath = String.format("descendant::%s[@id]", this._Fields.getName());
		List<?> elements = this._CoreElement.selectNodes(xpath);
		// this.logDebug("XML的SelectAll方法,得到后代节点个数:" + elements.size());
		for (Object node : elements)
		{
			HashMap<String, String> map = XDocument.toMapByAttributes((Node) node);
			T t = this.newBlankDomain();
			t.putAll(map);
			ts.add(t);
		}
		return ts;
	}

	@Override
	public XmlDomain update(final XmlDomain domain)
	{
		try
		{
			String xpath = String.format("//%s[@id='%s']", this._Fields.getName(), domain.getId());
			this.logDebug("Update XPath:" + xpath);
			Node node = this._CoreElement.selectSingleNode(xpath);
			Element element = (node == null) ? DocumentHelper.createElement(_Fields.getName()) : (Element) node;
			for (Entry<String, Object> keyvalue : domain.entrySet())
			{
				String value = this.conventValue(keyvalue.getValue());
				XDocument.setAttribute(element, keyvalue.getKey(), value);
			}
			XDocument.setAttribute(element, PublicField.MODIFED_TIME, Dates.nowToGeneralLongString());
			if (node == null)
				_CoreElement.add(element);
			this._OwnerDocument.save();
			return domain;
		}
		catch (Exception e)
		{
			if (null == domain)
				this.logWarn("实体为空。未修改。");
			else
			{
				Id id = domain.getId();
				if (null != id)
					this.logWarn("实体修改失效。原因请分析实体。" + domain.toJson());
				else
					this.logWarn(String.format("实体修改失效。ID值为null"));
			}
			return null;
		}
	}

	@Override
	public XmlDomain update(final Id modifiorId, final Id domainId, final String key, final Object value)
	{
		Element element = (Element) this._CoreElement.selectSingleNode(String.format("//%s[@id='%s']", this._Fields.getName(), domainId));

		XDocument.setAttribute(element, key, this.conventValue(value));
		XDocument.setAttribute(element, PublicField.MODIFIOR, modifiorId.toString());
		XDocument.setAttribute(element, PublicField.MODIFED_TIME, Dates.nowToGeneralLongString());
		this._OwnerDocument.save();
		return this.select(domainId);
	}

	@Override
	public XmlDomain update(final Id modifiorId, final Id domainId, final HashMap<String, Object> valueMap)
	{
		Element element = (Element) this._CoreElement.selectSingleNode(String.format("//%s[@id='%s']", this._Fields.getName(), domainId));
		for (Entry<String, Object> entry : valueMap.entrySet())
			XDocument.setAttribute(element, entry.getKey(), this.conventValue(entry.getValue()));
		XDocument.setAttribute(element, PublicField.MODIFIOR, modifiorId.toString());
		XDocument.setAttribute(element, PublicField.MODIFED_TIME, Dates.nowToGeneralLongString());
		this._OwnerDocument.save();
		return this.select(domainId);
	}

	@Override
	public boolean update(final Id modifiorId, final HashMap<String, Object> valueMap, final String xpath)
	{
		try
		{
			List<?> eleList = this._CoreElement.selectNodes(xpath);
			for (Object object : eleList)
			{
				if (object instanceof Element)
					continue;
				this.update(modifiorId, DId.ME(((Element) object).attributeValue(PublicField.ID)), valueMap);
			}
			return true;
		}
		catch (Exception e)
		{
			return false;
		}

	}

	/**
	 * 根据实体Id查询该实体的Name,当没有Name时，返回ID的字符串
	 * 
	 * @param id
	 * @return
	 */
	public String queryNameById(final Id id)
	{
		String xpath = String.format("//%s[@id='%s']/@name", this._Fields.getName(), id);
		Node node = this._CoreElement.selectSingleNode(xpath);
		if (null == node)
			return id.toString();
		return node.getText().trim();
	}

	// =========================
	public DomainList<XmlDomain> where(final String xpath)
	{
		DomainList<XmlDomain> ts = new DomainList<XmlDomain>();
		List<? extends Node> elements = this._CoreElement.selectNodes(xpath);
		for (Node node : elements)
		{
			if (!(node instanceof Element))
				continue;
			HashMap<String, String> map = XDocument.toMapByAttributes(node);
			T t = this.newBlankDomain();
			t.putAll(map);
			ts.add(t);
		}
		return ts;
	}

	// =========================
	/**
	 * 将Object转换为存储为XML的值时的字符串类型。
	 * 
	 * @param keyvalue
	 * @return
	 */
	private String conventValue(final Object keyvalue)
	{
		Object object = keyvalue;
		Mirror<?> mirror = Mirror.me(object);
		String value = "";
		if (object instanceof Id)
			value = ((Id) object).toString();
		else if (mirror.isDateTimeLike())
			value = Dates.formatGeneral((Date) object);
		else
			value = String.valueOf(object);
		return value;
	}

	/**
	 * 将指定Element的子节点(不含当该节点)置为被禁用状态
	 * 
	 * @param modifior
	 * @param element
	 */
	protected int deleteChildElement(final Id modifior, final Element element)
	{
		List<?> eleList = element.selectNodes(String.format("descendant::%s[@id]", this._Fields.getName()));
		for (Object object : eleList)
		{
			if (!(object instanceof Element))
				continue;
			Element subElement = (Element) object;
			XDocument.setAttribute(subElement, PublicField.IS_DELETED, "1");
			XDocument.setAttribute(subElement, PublicField.MODIFIOR, modifior.toString());
			XDocument.setAttribute(subElement, PublicField.MODIFED_TIME, Dates.nowToGeneralLongString());
		}
		return eleList.size();
	}
}
